package com.xy.lang.util;

import com.xy.lang.domain.BooleanHolder;
import com.xy.lang.domain.IntegerHolder;
import com.xy.lang.domain.Result;
import com.xy.lang.exception.BadRequestException;
import com.xy.lang.function.ForeachSubListConsumer;
import com.xy.lang.function.ForeachSubMapConsumer;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * extension functions for collection utilities
 *
 * Created by zsp on 2018/7/9.
 */
public class CollectionUtilsEx {

    public static final int DEFAULT_BATCH_VISIT_SIZE = 100;

    public static <TKey, TValue> void putListToMap(Map<TKey, List<TValue>> map, TKey key,
                                                   TValue value) {
        if (value == null) {
            return;
        }
        getListFromMap(map, key).add(value);
    }

    public static <TKey, TValue> void putListToMap(Map<TKey, List<TValue>> map, TKey key,
                                                   Collection<TValue> collection) {
        if (isEmpty(collection)) {
            return;
        }
        getListFromMap(map, key).addAll(collection);
    }

    public static <TKey, TValue> List<TValue> getListFromMap(Map<TKey, List<TValue>> map, TKey key) {
        Objects.requireNonNull(map);
        List<TValue> list = map.get(key);
        if (list == null) {
            list = new ArrayList<>();
            map.put(key, list);
        }
        return list;
    }

    public static <TType> boolean isEmpty(Collection<TType> col) {
        return col == null || col.size() == 0;
    }

    public static <TType> boolean isNotEmpty(Collection<TType> col) {
        return !isEmpty(col);
    }

    public static <TType> boolean isEmpty(TType... array) {
        return array == null || array.length == 0;
    }

    public static <TType> boolean isNotEmpty(TType... array) {
        return !isEmpty(array);
    }

    /**
     *
     */
    public static <TType, TField> Set<TField> listField(Collection<TType> col,
                                                        Function<TType, TField> fieldFetcher) {
        return listField(col, fieldFetcher, new Predicate[0]);
    }

    /**
     *
     */
    public static <TType, TField> Set<TField> listField(Collection<TType> col,
                                                        Function<TType, TField> fieldFetcher,
                                                        Predicate<TType>... filters) {
        Stream<TType> stream = buildStreamForlistField(col, fieldFetcher, filters);
        return stream.map(fieldFetcher).distinct().collect(Collectors.toSet());
    }

    /**
     *
     */
    public static <TType, TField> Collection<TField> listField(Collection<TType> col,
                                                               Function<TType, TField> fieldFetcher,
                                                               boolean distinct) {
        return listField(col, fieldFetcher, distinct, new Predicate[0]);
    }

    /**
     *
     */
    public static <TType, TField> Collection<TField> listField(Collection<TType> col,
                                                               Function<TType, TField> fieldFetcher,
                                                               boolean distinct,
                                                               Predicate<TType>... filters) {
        Stream<TType> stream = buildStreamForlistField(col, fieldFetcher, filters);
        if (distinct) {
            return stream.map(fieldFetcher).distinct().collect(Collectors.toSet());
        } else {
            return stream.map(fieldFetcher).collect(Collectors.toList());
        }
    }

    private static <TType, TField> Stream<TType> buildStreamForlistField(Collection<TType> col,
                                                                         Function<TType, TField> fieldFetcher,
                                                                         Predicate<TType>... filters) {
        Objects.requireNonNull(col, "The col is null.");
        Objects.requireNonNull(fieldFetcher, "The fieldFetcher is null.");
        Stream<TType> stream = col.stream();
        if (isNotEmpty(filters)) {
            for (Predicate<TType> filter : filters) {
                stream.filter(filter);
            }
        }
        return stream;
    }

    public static <E> void foreachSubList(List<E> list, int batchSize, ForeachSubListConsumer<E> delegate) {
        if (isEmpty(list)) {
            return;
        }
        Objects.requireNonNull(delegate, "The delegate is null.");
        if (batchSize < 1) {
            throw new IllegalArgumentException("The batchSize should be than 1.");
        }
        int loop = 0, startIndex = 0, len = list.size();
        for (; startIndex < len; loop++, startIndex += batchSize) {
            int endIndex = (startIndex + batchSize) > len ? len : startIndex + batchSize;
            List<E> subList = list.subList(startIndex, endIndex);
            delegate.foreach(loop, startIndex, endIndex, subList);
        }
    }

    public static <K, V> void foreachSubMap(Map<K, V> map, int batchSize, ForeachSubMapConsumer<K, V> delegate) {
        if (isEmpty(map)) {
            return;
        }
        Objects.requireNonNull(delegate, "The delegate is null.");
        if (batchSize < 1) {
            throw new IllegalArgumentException("The batchSize should be than 1.");
        }
        List<K> keyList = new ArrayList<>(map.keySet());
        List<K> subKeyList;
        Map<K, V> subMap;
        int loop = 0, startIndex = 0, len = keyList.size();
        for (; startIndex < len; loop++, startIndex += batchSize) {
            int endIndex = (startIndex + batchSize) > len ? len : startIndex + batchSize;
            subKeyList = keyList.subList(startIndex, endIndex);
            subMap = new HashMap<>();
            for (K key : subKeyList) {
                subMap.put(key, map.get(key));
            }
            delegate.foreach(loop, startIndex, endIndex, subMap);
        }
    }

    public static <E> List<E> batchFetchList(int batchSize, Function<Integer, List<E>> delegate) {
        return batchFetchList(batchSize, delegate,
                (subList) -> {
                    return isNotEmpty(subList);
                });
    }

    public static <E> List<E> batchFetchList(int batchSize,
                                             Function<Integer, List<E>> delegate,
                                             Predicate<List<E>> validator) {
        List<E> list = new ArrayList<>();
        for (int loop = 0; ; loop++) {
            List<E> subList = delegate.apply(loop);
            if (validator.test(subList)) {
                list.addAll(subList);
            }
            if (!validator.test(subList) || subList.size() < batchSize) {
                break;
            }
        }
        return list;
    }

    public static boolean batchForeach(Function<Integer, Boolean> delegate) {
        boolean affected = false;
        for (int loop = 0; ; loop++) {
            Boolean flag = delegate.apply(loop);
            if (flag == null || !flag) {
                break;
            }
            affected = true;
        }
        return affected;
    }

    /**
     * 构建map，要求键集合与值集合中的元素顺序一致对应
     *
     * @param keyCollection             键集合
     * @param valueCollectionFactory    值集合工厂对象
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K, V> Map<K, V> toMap(Collection<K> keyCollection,
                                         Function<Collection<K>, Collection<V>> valueCollectionFactory) {
        Objects.requireNonNull(valueCollectionFactory);
        return toMap(keyCollection, valueCollectionFactory.apply(keyCollection));
    }

    /**
     * 构建map，要求键集合与值集合中的元素顺序一致对应
     *
     * @param keyCollection             键集合
     * @param valueCollection           值集合
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K, V> Map<K, V> toMap(Collection<K> keyCollection, Collection<V> valueCollection) {
        if (keyCollection == null || keyCollection.size() == 0) {
            return Collections.emptyMap();
        }
        if (valueCollection == null || keyCollection.size() != valueCollection.size()) {
            throw new RuntimeException("The size of key collection is not equal to the size of value collection.");
        }
        Iterator<V> valueIt = valueCollection.iterator();
        Map<K, V> map = new HashMap<>(keyCollection.size());
        for (K key : keyCollection) {
            map.put(key, valueIt.next());
        }
        return map;
    }

    /**
     * 根据第二个集合分支第一个集合，返回三个集合分支：
     * 包括交集（相同的元素）、增集（增加的元素）、差集（删除的元素）
     *
     * @param    source            第一个集合
     * @param    target            第二个集合
     * @return 顺序返回3个列表集合：交集、增集、差集。
     */
    public static <T> List<List<T>> fork(List<T> source, List<T> target) {
        return fork(source, target, null);
    }

    /**
     * 根据第二个集合分支第一个集合，返回三个集合分支：
     * 包括交集（相同的元素）、增集（增加的元素）、差集（删除的元素）
     *
     * @param    source            第一个集合
     * @param    target            第二个集合
     * @param    classifier        获取集合元素标识字段（例如ID）的代理方法
     * @return 顺序返回3个列表集合：交集、增集、差集。
     */
    public static <T, K> List<List<T>> fork(List<T> source, List<T> target, Function<T, K> classifier) {
        return fork(source, target, classifier, null, null);
    }

    /**
     * 根据第二个集合分支第一个集合，返回三个集合分支：
     * 包括交集（相同的元素）、增集（增加的元素）、差集（删除的元素）
     *
     * @param    source                        第一个集合
     * @param    target                        第二个集合
     * @param    classifier                    获取集合元素标识字段（例如ID）的代理方法
     * @param    intersectionConsumer        在第一集合和第二集合有相同的元素时的访问方法
     * @param    addConsumer                    第二集合有新增加的元素时的访问方法
     * @return 顺序返回3个列表集合：交集、增集、差集。
     */
    public static <T, K> List<List<T>> fork(List<T> source,
                                            List<T> target,
                                            Function<T, K> classifier,
                                            BiConsumer<T, T> intersectionConsumer,
                                            Consumer<T> addConsumer) {
        if (source == null) {
            throw new NullPointerException("The source is null.");
        }
        Function<T, K> classifier0 = (classifier == null) ? (e) -> (K) e : classifier;
        BiConsumer<T, T> intersectionConsumer0 = intersectionConsumer == null ? (sourceItem, targetItem) -> {
        } : intersectionConsumer;
        Consumer<T> addConsumer0 = addConsumer == null ? (addItem) -> {
        } : addConsumer;

        List<T> intersectionList = new ArrayList<>();
        List<T> addList = new ArrayList<>();
        if (target == null || target.size() == 0) {
            return Arrays.asList(intersectionList, addList, source);
        }
        List<T> subtractList = new ArrayList<>();
        Map<K, T> map = source.stream().collect(Collectors.toMap(classifier0, item -> item));
        for (T element : target) {
            K key = classifier0.apply(element);
            if (map.containsKey(key)) {
                intersectionList.add(element);
                intersectionConsumer0.accept(map.get(key), element);
            } else {
                addList.add(element);
                addConsumer0.accept(element);
            }
            map.remove(key);
        }
        if (map.size() > 0) {
            subtractList.addAll(map.values());
        }

        return Arrays.asList(intersectionList, addList, subtractList);
    }

    /**
     *
     * @param collection
     * @param idGetter
     * @param parentIdGetter
     * @param parentObjectListGetter
     * @param parentObjectSetter
     * @param <T>
     */
    public static <T> void foreachSetParent(Collection<T> collection,
                                            Function<T, Long> idGetter,
                                            Function<T, Long> parentIdGetter,
                                            Function<Collection<Long>, Collection<T>> parentObjectListGetter,
                                            BiConsumer<T, T> parentObjectSetter) {
        Map<Long, T> map = new HashMap<>();
        Set<Long> parentIdSet = new HashSet<>();
        Long parentId;
        for (T obj : collection) {
            map.put(idGetter.apply(obj), obj);
            parentId = parentIdGetter.apply(obj);
            if (parentId != null && !Objects.equals(0L, parentId)) {
                parentIdSet.add(parentId);
            }
        }
        if (parentIdSet.removeAll(map.keySet()) && !parentIdSet.isEmpty()) {
            Collection<T> tempCollection = parentObjectListGetter.apply(parentIdSet);
            for (T obj : tempCollection) {
                map.put(idGetter.apply(obj), obj);
            }
        }
        for (T obj : collection) {
            parentId = parentIdGetter.apply(obj);
            if (parentId != null && !Objects.equals(0L, parentId)) {
                parentObjectSetter.accept(obj, map.get(parentId));
            }
        }
    }

    public static <S, T, TField> void buildCollection(Collection<S> sourceCollection,
                                                      Function<S, TField> sourceGetter,
                                                      Collection<T> targetCollection,
                                                      Function<T, TField> targetGetter,
                                                      BiConsumer<S, T> targetSetter) {
        Objects.requireNonNull(sourceCollection);
        Objects.requireNonNull(sourceGetter);
        Objects.requireNonNull(targetCollection);
        Objects.requireNonNull(targetGetter);
        Objects.requireNonNull(targetSetter);

        Map<TField, S> sourceMap = sourceCollection.stream().collect(Collectors.toMap(sourceGetter, e -> e));
        for (T target : targetCollection) {
            targetSetter.accept(sourceMap.get(targetGetter.apply(target)), target);
        }
    }

    /**
     * 为父元素设置子元素
     *
     * @param parentCollection              父元素集合
     * @param parentIdGetter                父元素ID获取方法
     * @param childrenGetter                子元素集合获取方法
     * @param parentIdGetterFromChild       父元素ID获取方法，从子元素上获取
     * @param childrenSetter                子元素集合设置方法，给父元素设置子元素
     * @param <P>                           父元素类型
     * @param <C>                           子元素类型
     * @param <ID>                          ID类型
     */
    public static <P, C, ID> void setChildrenForParent(Collection<P> parentCollection,
                                                       Function<P, ID> parentIdGetter,
                                                       Function<Set<ID>, List<C>> childrenGetter,
                                                       Function<C, ID> parentIdGetterFromChild,
                                                       BiConsumer<P, List<C>> childrenSetter) {
        Objects.requireNonNull(parentIdGetter);
        Objects.requireNonNull(childrenGetter);
        Objects.requireNonNull(parentIdGetterFromChild);
        Objects.requireNonNull(childrenSetter);

        if (parentCollection != null && parentCollection.size() > 0) {
            Set<ID> parentIdSet = parentCollection.stream().map(parentIdGetter).collect(Collectors.toSet());
            if (parentIdSet == null || parentIdSet.size() == 0 || parentIdSet.stream().anyMatch(e -> Objects.isNull(e))) {
                throw new BadRequestException("The parentIdGetter is wrong function.");
            }
            List<C> allChildren = childrenGetter.apply(parentIdSet);
            if (allChildren != null && allChildren.size() > 0) {
                Map<ID, List<C>> childrenGroup = allChildren.stream().collect(Collectors.groupingBy(parentIdGetterFromChild));
                parentCollection.forEach(e -> {
                    childrenSetter.accept(e, childrenGroup.get(parentIdGetter.apply(e)));
                });
            }
        }
    }

    public static interface SequenceObject {

        Integer getSequence();

        void setSequence(Integer sequence);
    }

    public static List<SequenceObject> swapSort(Collection<? extends SequenceObject> collection,
                                                int sourceSequence,
                                                int targetSequence) {
        Objects.requireNonNull(sourceSequence);
        Objects.requireNonNull(targetSequence);
        int len = -1;
        if (collection == null || (len = collection.size()) == 0) {
            throw new IllegalArgumentException("The collection is required.");
        }
        if (sourceSequence < 0 || sourceSequence > len || targetSequence < 0 || targetSequence > len) {
            throw new IllegalArgumentException("The sequence should be between 1 and " + len);
        }
        if (Objects.equals(sourceSequence, targetSequence)) {
            return Collections.emptyList();
        }

        Integer tempSequence = targetSequence;
        List<SequenceObject> list = new ArrayList<>(collection);
        SequenceObject source = list.get(sourceSequence - 1);
        SequenceObject target = list.get(targetSequence - 1);
        int currentSequence = 0;
        SequenceObject current;
        List<SequenceObject> filterList = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            currentSequence = i + 1;
            if (source.getSequence() < target.getSequence()) {
                if (currentSequence > source.getSequence() && currentSequence <= target.getSequence()) {
                    current = list.get(i);
                    current.setSequence(current.getSequence() - 1);
                    filterList.add(current);
                }
            } else {
                if (currentSequence >= target.getSequence() && currentSequence < source.getSequence()) {
                    current = list.get(i);
                    current.setSequence(current.getSequence() + 1);
                    filterList.add(current);
                }
            }
        }
        source.setSequence(tempSequence);
        filterList.add(source);
        return filterList;
    }

    public static <T> List<T> quickSort(Collection<T> collection,
                                        int removeSequence,
                                        BiConsumer<T, Integer> sequenceSetter) {
        Objects.requireNonNull(sequenceSetter);
        if (Objects.isNull(collection) || collection.isEmpty()) {
            return Collections.emptyList();
        }
        if (removeSequence >= collection.size() || removeSequence < 0) {
            return Collections.emptyList();
        }
        List<T> list = new ArrayList<>(collection);
        List<T> filterList = new ArrayList<>();
        for (int i = removeSequence; i < list.size(); i++) {
            sequenceSetter.accept(list.get(i), i);
            filterList.add(list.get(i));
        }
        return filterList;
    }

    public static <T> List<T> swapSort(Collection<T> collection,
                                       int sourceSequence,
                                       int targetSequence,
                                       Function<T, Integer> sequenceGetter,
                                       BiConsumer<T, Integer> sequenceSetter) {
        Objects.requireNonNull(sequenceGetter);
        Objects.requireNonNull(sequenceSetter);
        int len = -1;
        if (collection == null || (len = collection.size()) == 0) {
            throw new IllegalArgumentException("The collection is required.");
        }
        if (sourceSequence < 0 || sourceSequence > len || targetSequence < 0 || targetSequence > len) {
            throw new IllegalArgumentException("The sequence should be between 1 and " + len);
        }
        if (Objects.equals(sourceSequence, targetSequence)) {
            return Collections.emptyList();
        }

        Integer tempSequence = targetSequence;
        List<T> list = new ArrayList<>(collection);
        T source = list.get(sourceSequence - 1);
        T target = list.get(targetSequence - 1);
        int currentSequence;
        T current;
        List<T> filterList = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            currentSequence = i + 1;
            if (sequenceGetter.apply(source) < sequenceGetter.apply(target)) {
                if (currentSequence > sequenceGetter.apply(source) && currentSequence <= sequenceGetter.apply(target)) {
                    current = list.get(i);
                    sequenceSetter.accept(current, sequenceGetter.apply(current) - 1);
                    filterList.add(current);
                }
            } else {
                if (currentSequence >= sequenceGetter.apply(target) && currentSequence < sequenceGetter.apply(source)) {
                    current = list.get(i);
                    sequenceSetter.accept(current, sequenceGetter.apply(current) + 1);
                    filterList.add(current);
                }
            }
        }
        sequenceSetter.accept(source, tempSequence);
        filterList.add(source);
        return filterList;
    }

    static class SequenceObjectImpl implements SequenceObject {

        private Integer sequence = 0;

        SequenceObjectImpl(Integer sequence) {
            this.sequence = sequence;
        }

        @Override
        public Integer getSequence() {
            return sequence;
        }

        @Override
        public void setSequence(Integer sequence) {
            this.sequence = sequence;
        }
    }

    static class SequenceObjectImpl2 {

        private Integer sequence = 0;

        SequenceObjectImpl2(Integer sequence) {
            this.sequence = sequence;
        }

        public Integer getSequence() {
            return sequence;
        }

        public void setSequence(Integer sequence) {
            this.sequence = sequence;
        }
    }

    public static void main(String[] args) {
//        List<SequenceObject> list = new ArrayList<>();
//        list.add(new SequenceObjectImpl(1));
//        list.add(new SequenceObjectImpl(2));
//        list.add(new SequenceObjectImpl(3));
//        list.add(new SequenceObjectImpl(4));
//        list.add(new SequenceObjectImpl(5));
        //List<SequenceObject> filterList = swapSort(list, 2, 4);
        //List<SequenceObject> filterList = swapSort(list, 4, 2);
        //List<SequenceObject> filterList = swapSort(list, -1, 2);
        //List<SequenceObject> filterList = swapSort(list, 2, 6);
        //List<SequenceObject> filterList = swapSort(list, 1, 5);
        //List<SequenceObject> filterList = swapSort(list, 5, 1);
//        for(SequenceObject item : list) {
//            System.out.println(item.getSequence());
//        }
//        System.out.println("--------");
//        for(SequenceObject item : filterList) {
//            System.out.println(item.getSequence());
//        }

        List<SequenceObjectImpl2> list2 = new ArrayList<>();
        list2.add(new SequenceObjectImpl2(1));
        list2.add(new SequenceObjectImpl2(2));
        list2.add(new SequenceObjectImpl2(3));
        list2.add(new SequenceObjectImpl2(4));
        list2.add(new SequenceObjectImpl2(5));
//        List<SequenceObjectImpl2> filterList2 = swapSort(list2, 5, 1,
//                SequenceObjectImpl2::getSequence, SequenceObjectImpl2::setSequence);
//        for (SequenceObjectImpl2 item : list2) {
//            System.out.println(item.getSequence());
//        }
//        System.out.println("--------");
//        for (SequenceObjectImpl2 item : filterList2) {
//            System.out.println(item.getSequence());
//        }
        List<SequenceObjectImpl2> sequenceObjectImpl2s = quickSort(list2, 9, SequenceObjectImpl2::setSequence);
        sequenceObjectImpl2s.forEach(e -> System.out.println(e.sequence));

    }

}
